QuickOPC User's Guide and Reference
Use the state instead of handles to identify subscribed entities
Best Practices > Use the state instead of handles to identify subscribed entities
In This Topic

General

The handles returned from any EasyXXClient.SubscribeXXXX methods are only meant for use as input arguments to other methods that manipulate the existing subscriptions – such as EasyXXClient.ChangeXXXX or EasyXXClient.UnsubscribeXXXX.

If you need to identify the subscribed entities inside event handlers, use the ‘state’ argument that can be passed to EasyXXClient.SubscribeXXXX methods (or a State property on XXXXArguments objects). You can pass any type of object in the state, as it is of type System.Object (and then cast the System.Object back to the original type in the event handler).

Why shouldn’t the handles be used? There are multiple reasons for that:

  1. The SubscribeXXXX methods are asynchronous. There is no guaranteed relation between the moment the SubscribeXXXX method returns, and the moment when events start flowing into your event handler. The events can start flowing before the methods returns. Or, they can start flowing after the method returns, but before you get a chance to process the returned handles, and store them somewhere where the event handler can access them.
  2. Similarly, UnsubscribeXXXX methods are asynchronous as well, and therefore you cannot know precisely when any event handle became invalid. You may (temporarily) receive events for a previously returned handles, even after the UnsubscribeXXXX method finishes.
  3. With handles, you will need to set up and properly maintain an additional data structure – something that keeps a correspondence between the handles returned (whose values you do not know in advance), and your application data that relate to these handles. Typically, this data structure is a dictionary. Maintaining and accessing this additional data structure means extra code, and inefficient access (application data must be looked up by handle from inside the event handler).
  4. Using the handles returned from SubscribeXXXX in an event handler requires coordination of the access to a data structure where the handles are stored – i.e. an inter-thread synchronization. This leads to complicated and often buggy code, with risk of deadlocks or invalid data being accessed.

All these disadvantages can simply be overcome by passing the application data needed into the SubscribeXXXX method as a state object, and then casting the State to the appropriate type and using directly inside the event handler.

Note that earlier versions passed the handle to your code in the event arguments, making it relatively easy to make a mistake of using them from inside the handler. This is no longer the case with the current version, and the likeliness of violating this best practice is therefore much lower. With extra effort, you might be able to somehow “stick” the handle into the event notification, possibly by back-filling the State property after the subscription was made. It is strongly recommended that you do not do that for the reasons explained above.

Examples for OPC Classic 

// This example shows how subscribe to changes of multiple items and display each change, identifying the different
// subscriptions by an integer.

using System;
using System.Threading;
using OpcLabs.EasyOpc.DataAccess;
using OpcLabs.EasyOpc.DataAccess.OperationModel;

namespace DocExamples.DataAccess._EasyDAClient
{
    partial class SubscribeMultipleItems
    {
        public static void StateAsInteger()
        {
            // Instantiate the client object.
            using (var client = new EasyDAClient())
            {
                // Hook events
                client.ItemChanged += client_StateAsInteger_ItemChanged;

                Console.WriteLine("Subscribing...");
                int[] handleArray = client.SubscribeMultipleItems(new[]
                {
                    new DAItemGroupArguments("", "OPCLabs.KitServer.2", "Simulation.Random", 1000,
                        state: 1), // An integer we have chosen to identify the subscription
                    new DAItemGroupArguments("", "OPCLabs.KitServer.2", "Trends.Ramp (1 min)", 1000,
                        state: 2), // An integer we have chosen to identify the subscription
                    new DAItemGroupArguments("", "OPCLabs.KitServer.2", "Trends.Sine (1 min)", 1000,
                        state: 3), // An integer we have chosen to identify the subscription
                    new DAItemGroupArguments("", "OPCLabs.KitServer.2", "Simulation.Register_I4", 1000,
                        state: 4) // An integer we have chosen to identify the subscription
                });

                for (int i = 0; i < handleArray.Length; i++)
                    Console.WriteLine($"handleArray[{i}]: {handleArray[i]}");

                Console.WriteLine("Processing item changed events for 10 seconds...");
                Thread.Sleep(10 * 1000);

                Console.WriteLine("Unsubscribing...");
            }

            Console.WriteLine("Waiting for 5 seconds...");
            Thread.Sleep(5 * 1000);

            Console.WriteLine("Finished.");
        }

        // Item changed event handler
        static void client_StateAsInteger_ItemChanged(object sender, EasyDAItemChangedEventArgs eventArgs)
        {
            // Obtain the integer state we have passed in.
            var stateAsInteger = (int)eventArgs.Arguments.State;

            // Display the data
            if (eventArgs.Succeeded)
                Console.WriteLine($"{stateAsInteger}: {eventArgs.Vtq}");
            else
                Console.WriteLine($"{stateAsInteger} *** Failure: {eventArgs.ErrorMessageBrief}");
        }
    }
}
# This example shows how subscribe to changes of multiple items and display each change, identifying the different
# subscriptions by an integer.

# The QuickOPC package is needed. Install it using "pip install opclabs_quickopc".
import opclabs_quickopc
import time

# Import .NET namespaces.
from OpcLabs.EasyOpc.DataAccess import *
from OpcLabs.EasyOpc.DataAccess.OperationModel import *


# Item changed event handler.
def itemChanged(sender, eventArgs):
    # Obtain the integer state we have passed in.
    stateAsInteger = int(eventArgs.Arguments.State)
    if eventArgs.Succeeded:
        print(stateAsInteger, ': ', eventArgs.Vtq, sep='')
    else:
        print(stateAsInteger, ' *** Failure: ', eventArgs.ErrorMessageBrief, sep='')


# Instantiate the client object.
client = EasyDAClient()
# Hook events.
client.ItemChanged += itemChanged

print('Subscribing item changes...')
handleArray = IEasyDAClientExtension.SubscribeMultipleItems(client, [
    DAItemGroupArguments('', 'OPCLabs.KitServer.2', 'Simulation.Random', 1000,
                         1),    # an integer we have chosen to identify the subscription
    DAItemGroupArguments('', 'OPCLabs.KitServer.2', 'Trends.Ramp (1 min)', 1000,
                         2),    # an integer we have chosen to identify the subscription
    DAItemGroupArguments('', 'OPCLabs.KitServer.2', 'Trends.Sine (1 min)', 1000,
                         3),    # an integer we have chosen to identify the subscription
    DAItemGroupArguments('', 'OPCLabs.KitServer.2', 'Simulation.Register_I4', 1000,
                         4),    # an integer we have chosen to identify the subscription
    ])

for i in range(len(handleArray)):
    print('handleArray[', i, ']: ', handleArray[i], sep='')

print('Processing item change notifications for 1 minute...')
time.sleep(60)

print('Unsubscribing all items...')
client.UnsubscribeAllItems()

client.ItemChanged -= itemChanged

print('Finished.')

 

 

// This example shows how subscribe to changes of multiple items and display each change, identifying the different
// subscriptions by an object.

using System;
using System.Threading;
using OpcLabs.EasyOpc.DataAccess;
using OpcLabs.EasyOpc.DataAccess.OperationModel;

namespace DocExamples.DataAccess._EasyDAClient
{
    partial class SubscribeMultipleItems
    {
        class CustomObject
        {
            public CustomObject(string name)
            {
                Name = name;
            }

            public string Name { get; }
        }


        public static void StateAsObject()
        {
            // Instantiate the client object.
            using (var client = new EasyDAClient())
            {
                // Hook events
                client.ItemChanged += client_StateAsObject_ItemChanged;

                Console.WriteLine("Subscribing...");
                int[] handleArray = client.SubscribeMultipleItems(new[]
                {
                    new DAItemGroupArguments("", "OPCLabs.KitServer.2", "Simulation.Random", 1000,
                        new CustomObject("First")), // A custom object that corresponds to the subscription
                    new DAItemGroupArguments("", "OPCLabs.KitServer.2", "Trends.Ramp (1 min)", 1000,
                        new CustomObject("Second")), // A custom object that corresponds to the subscription
                    new DAItemGroupArguments("", "OPCLabs.KitServer.2", "Trends.Sine (1 min)", 1000,
                        new CustomObject("Third")), // A custom object that corresponds to the subscription
                    new DAItemGroupArguments("", "OPCLabs.KitServer.2", "Simulation.Register_I4", 1000,
                        new CustomObject("Fourth")) // A custom object that corresponds to the subscription
                });

                for (int i = 0; i < handleArray.Length; i++)
                    Console.WriteLine($"handleArray[{i}]: {handleArray[i]}");

                Console.WriteLine("Processing item changed events for 10 seconds...");
                Thread.Sleep(10 * 1000);

                Console.WriteLine("Unsubscribing...");
            }

            Console.WriteLine("Waiting for 5 seconds...");
            Thread.Sleep(5 * 1000);

            Console.WriteLine("Finished.");
        }

        // Item changed event handler
        static void client_StateAsObject_ItemChanged(object sender, EasyDAItemChangedEventArgs eventArgs)
        {
            // Obtain the custom object we have passed in.
            var stateAsObject = (CustomObject)eventArgs.Arguments.State;

            // Display the data
            if (eventArgs.Succeeded)
                Console.WriteLine($"{stateAsObject.Name}: {eventArgs.Vtq}");
            else
                Console.WriteLine($"{stateAsObject.Name} *** Failure: {eventArgs.ErrorMessageBrief}");
        }
    }
}
# This example shows how subscribe to changes of multiple items and display each change, identifying the different
# subscriptions by an object.

# The QuickOPC package is needed. Install it using "pip install opclabs_quickopc".
import opclabs_quickopc
import time

# Import .NET namespaces.
from OpcLabs.EasyOpc.DataAccess import *
from OpcLabs.EasyOpc.DataAccess.OperationModel import *


class CustomObject(object):
    def __init__(self, name):
        self.name = name


# Item changed event handler.
def itemChanged(sender, eventArgs):
    # Obtain the custom object we have passed in.
    stateAsObject = eventArgs.Arguments.State
    if eventArgs.Succeeded:
        print(stateAsObject.name, ': ', eventArgs.Vtq, sep='')
    else:
        print(stateAsObject.name, ' *** Failure: ', eventArgs.ErrorMessageBrief, sep='')


# Instantiate the client object.
client = EasyDAClient()
# Hook events.
client.ItemChanged += itemChanged

print('Subscribing item changes...')
handleArray = IEasyDAClientExtension.SubscribeMultipleItems(client, [
    DAItemGroupArguments('', 'OPCLabs.KitServer.2', 'Simulation.Random', 1000,
                         CustomObject('First')),    # a custom object that corresponds to the subscription
    DAItemGroupArguments('', 'OPCLabs.KitServer.2', 'Trends.Ramp (1 min)', 1000,
                         CustomObject('Second')),   # a custom object that corresponds to the subscription
    DAItemGroupArguments('', 'OPCLabs.KitServer.2', 'Trends.Sine (1 min)', 1000,
                         CustomObject('Third')),    # a custom object that corresponds to the subscription
    DAItemGroupArguments('', 'OPCLabs.KitServer.2', 'Simulation.Register_I4', 1000,
                         CustomObject('Fourth')),   # a custom object that corresponds to the subscription
    ])

for i in range(len(handleArray)):
    print('handleArray[', i, ']: ', handleArray[i], sep='')

print('Processing item change notifications for 1 minute...')
time.sleep(60)

print('Unsubscribing all items...')
client.UnsubscribeAllItems()

client.ItemChanged -= itemChanged

print('Finished.')

 

 

Examples for OPC Unified Architecture

.NET

// This example shows how to subscribe to changes of multiple monitored items
// and display each change, identifying the different subscriptions by an
// integer.

using System;
using System.Threading;
using OpcLabs.EasyOpc.UA;
using OpcLabs.EasyOpc.UA.OperationModel;

namespace UADocExamples._EasyUAClient
{
    partial class SubscribeMultipleMonitoredItems
    {
        public static void StateAsInteger()
        {
            UAEndpointDescriptor endpointDescriptor =
                "opc.tcp://opcua.demo-this.com:51210/UA/SampleServer";
            // or "http://opcua.demo-this.com:51211/UA/SampleServer" (currently not supported)
            // or "https://opcua.demo-this.com:51212/UA/SampleServer/"
            
            // Instantiate the client object and hook events.
            var client = new EasyUAClient();
            client.DataChangeNotification += ClientOnDataChangeNotification_StateAsInteger;

            Console.WriteLine("Subscribing...");
            int[] handleArray = client.SubscribeMultipleMonitoredItems(new[]
                {
                    new EasyUAMonitoredItemArguments(null, endpointDescriptor, 
                        "nsu=http://test.org/UA/Data/ ;i=10845", 1000)
                        {State = 1},    // An integer we have chosen to identify the subscription
                    new EasyUAMonitoredItemArguments(null, endpointDescriptor, 
                        "nsu=http://test.org/UA/Data/ ;i=10853", 1000)
                        {State = 2},    // An integer we have chosen to identify the subscription
                    new EasyUAMonitoredItemArguments(null, endpointDescriptor, 
                        "nsu=http://test.org/UA/Data/ ;i=10855", 1000)
                        {State = 3}     // An integer we have chosen to identify the subscription
                });

            for (int i = 0; i < handleArray.Length; i++)
                Console.WriteLine($"handleArray[{i}]: {handleArray[i]}");

            Console.WriteLine("Processing monitored item changed events for 10 seconds...");
            Thread.Sleep(10 * 1000);

            Console.WriteLine("Unsubscribing...");
            client.UnsubscribeAllMonitoredItems();

            Console.WriteLine("Waiting for 5 seconds...");
            Thread.Sleep(5 * 1000);

            Console.WriteLine("Finished.");
        }

        static void ClientOnDataChangeNotification_StateAsInteger(object sender, EasyUADataChangeNotificationEventArgs eventArgs)
        {
            // Obtain the integer state we have passed in.
            var stateAsInteger = (int) eventArgs.Arguments.State;

            // Display the data.
            if (eventArgs.Succeeded)
                Console.WriteLine($"{stateAsInteger}: {eventArgs.AttributeData}");
            else
                Console.WriteLine($"{stateAsInteger} *** Failure: {eventArgs.ErrorMessageBrief}");
        }
    }
}
# This example shows how to subscribe to changes of multiple monitored items
# and display each change, identifying the different subscriptions by an
# integer.

#requires -Version 5.1
using namespace OpcLabs.EasyOpc.UA
using namespace OpcLabs.EasyOpc.UA.OperationModel

# The path below assumes that the current directory is [ProductDir]/Examples-NET/PowerShell/Windows .
Add-Type -Path "../../../Components/Opclabs.QuickOpc/net472/OpcLabs.EasyOpcUA.dll"
Add-Type -Path "../../../Components/Opclabs.QuickOpc/net472/OpcLabs.EasyOpcUAComponents.dll"

[UAEndpointDescriptor]$endpointDescriptor =
    "opc.tcp://opcua.demo-this.com:51210/UA/SampleServer"
# or "http://opcua.demo-this.com:51211/UA/SampleServer" (currently not supported)
# or "https://opcua.demo-this.com:51212/UA/SampleServer/"

# Instantiate the client object.
$client = New-Object EasyUAClient

# Data change notification handler
Register-ObjectEvent -InputObject $client -EventName DataChangeNotification -Action { 
    # Obtain the integer state we have passed in.
    [int]$stateAsInteger = $EventArgs.Arguments.State

    # Display the data.
    if ($EventArgs.Succeeded) {
        Write-Host "$($stateAsinteger): $($EventArgs.AttributeData)"
    }
    else {
        Write-Host "$($stateAsinteger) *** Failure: $($EventArgs.ErrorMessageBrief)"
    }
}

Write-Host "Subscribing..."
$handleArray = $client.SubscribeMultipleMonitoredItems(@(
    (New-Object UAMonitoredItemArguments(
        (New-Object UAAttributeArguments($endpointDescriptor, [UANodeDescriptor]"nsu=http://test.org/UA/Data/ ;i=10845")), 
        1000) -Property @{State = 1}), # An integer we have chosen to identify the subscription
    (New-Object UAMonitoredItemArguments(
        (New-Object UAAttributeArguments($endpointDescriptor, [UANodeDescriptor]"nsu=http://test.org/UA/Data/ ;i=10853")), 
        1000) -Property @{State = 2}), # An integer we have chosen to identify the subscription
    (New-Object UAMonitoredItemArguments(
        (New-Object UAAttributeArguments($endpointDescriptor, [UANodeDescriptor]"nsu=http://test.org/UA/Data/ ;i=10855")),
        1000) -Property @{State = 3}) # An integer we have chosen to identify the subscription
    ))

for ($i = 0; $i -lt $handleArray.Length; $i++) {
    Write-Host "handleArray[$($i)]: $($handleArray[$i])"
}

Write-Host
Write-Host "Processing monitored item changed events for 10 seconds..."
$stopwatch =  [System.Diagnostics.Stopwatch]::StartNew() 
while ($stopwatch.Elapsed.TotalSeconds -lt 10) {    
    Start-Sleep -Seconds 1
}

Write-Host "Unsubscribing..."
$client.UnsubscribeAllMonitoredItems()

Write-Host "Waiting for 5 seconds..."
Start-Sleep -Seconds 5

Write-Host "Finished."
# This example shows how to subscribe to changes of multiple monitored items
# and display each change, identifying the different subscriptions by an
# integer.

# The QuickOPC package is needed. Install it using "pip install opclabs_quickopc".
import opclabs_quickopc
import time

# Import .NET namespaces.
from OpcLabs.EasyOpc.UA import *
from OpcLabs.EasyOpc.UA.OperationModel import *


def dataChangeNotification(sender, eventArgs):
    # Obtain the integer state we have passed in.
    stateAsInteger = int(eventArgs.Arguments.State)
    # Display value.
    if eventArgs.Succeeded:
        print(stateAsInteger, ': ', eventArgs.AttributeData.Value, sep='')
    else:
        print(stateAsInteger, ' *** Failure: ', eventArgs.ErrorMessageBrief, sep='')


endpointDescriptor = UAEndpointDescriptor('opc.tcp://opcua.demo-this.com:51210/UA/SampleServer')
# or 'http://opcua.demo-this.com:51211/UA/SampleServer' (currently not supported)
# or 'https://opcua.demo-this.com:51212/UA/SampleServer/'

# Instantiate the client object and hook events.
client = EasyUAClient()
client.DataChangeNotification += dataChangeNotification

print('Subscribing...')
monitoredItemArguments1 = EasyUAMonitoredItemArguments(
    None,
    endpointDescriptor,
    UANodeDescriptor('nsu=http://test.org/UA/Data/ ;i=10845'),
    UAMonitoringParameters(1000))
monitoredItemArguments1.State = 1   # an integer we have chosen to identify the subscription
monitoredItemArguments2 = EasyUAMonitoredItemArguments(
    None,
    endpointDescriptor,
    UANodeDescriptor('nsu=http://test.org/UA/Data/ ;i=10853'),
    UAMonitoringParameters(1000))
monitoredItemArguments2.State = 2   # an integer we have chosen to identify the subscription
monitoredItemArguments3 = EasyUAMonitoredItemArguments(
    None,
    endpointDescriptor,
    UANodeDescriptor('nsu=http://test.org/UA/Data/ ;i=10855'),
    UAMonitoringParameters(1000))
monitoredItemArguments3.State = 3   # an integer we have chosen to identify the subscription

client.SubscribeMultipleMonitoredItems([
    monitoredItemArguments1,
    monitoredItemArguments2,
    monitoredItemArguments3,
    ])

print('Processing data change events for 10 seconds...')
time.sleep(10)

print('Unsubscribing...')
client.UnsubscribeAllMonitoredItems()

print('Waiting for 5 seconds...')
time.sleep(5)

print('Finished.')
' This example shows how to subscribe to changes of multiple monitored items
' and display each change, identifying the different subscriptions by an
' integer.

Imports OpcLabs.EasyOpc.UA
Imports OpcLabs.EasyOpc.UA.OperationModel

Namespace _EasyUAClient
    Partial Friend Class SubscribeMultipleMonitoredItems
        Public Shared Sub StateAsInteger()

            ' Define which server we will work with.
            Dim endpointDescriptor As UAEndpointDescriptor =
                    "opc.tcp://opcua.demo-this.com:51210/UA/SampleServer"
            ' or "http://opcua.demo-this.com:51211/UA/SampleServer" (currently not supported)
            ' or "https://opcua.demo-this.com:51212/UA/SampleServer/"

            ' Instantiate the client object and hook events
            Dim client = New EasyUAClient()
            AddHandler client.DataChangeNotification, AddressOf ClientOnDataChangeNotification_StateAsInteger

            Console.WriteLine("Subscribing...")
            Dim handleArray() As Integer = client.SubscribeMultipleMonitoredItems(New EasyUAMonitoredItemArguments() _
                { _
                    New EasyUAMonitoredItemArguments(Nothing, endpointDescriptor, _
                        "nsu=http://test.org/UA/Data/ ;i=10845", 1000) _
                         With {.State = 1}, _
                    New EasyUAMonitoredItemArguments(Nothing, endpointDescriptor, _
                        "nsu=http://test.org/UA/Data/ ;i=10853", 1000) _
                         With {.State = 2}, _
                    New EasyUAMonitoredItemArguments(Nothing, endpointDescriptor, _
                        "nsu=http://test.org/UA/Data/ ;i=10855", 1000) _
                        With {.State = 3} _
                } _
             ) ' An integer we have chosen to identify the subscription

            For i As Integer = 0 To handleArray.Length - 1
                Console.WriteLine("handleArray[{0}]: {1}", i, handleArray(i))
            Next i

            Console.WriteLine("Processing monitored item changed events for 10 seconds...")
            Threading.Thread.Sleep(10 * 1000)

            Console.WriteLine("Unsubscribing...")
            client.UnsubscribeAllMonitoredItems()

            Console.WriteLine("Waiting for 5 seconds...")
            Threading.Thread.Sleep(5 * 1000)

            Console.WriteLine("Finished.")
        End Sub

        Private Shared Sub ClientOnDataChangeNotification_StateAsInteger(ByVal sender As Object, ByVal eventArgs As EasyUADataChangeNotificationEventArgs)
            ' Obtain the integer state we have passed in.
            Dim stateAsInteger As Integer = CInt(eventArgs.Arguments.State)

            ' Display the data
            If eventArgs.Succeeded Then
                Console.WriteLine("{0}: {1}", stateAsInteger, eventArgs.AttributeData)
            Else
                Console.WriteLine("{0} *** Failure: {1}", stateAsInteger, eventArgs.ErrorMessageBrief)
            End If
        End Sub
    End Class
End Namespace

COM

// This example shows how to subscribe to changes of multiple monitored items
// and display each change, identifying the different subscriptions by an
// integer.

type
  TIntegerClientEventHandlers124 = class
    procedure OnDataChangeNotification(
      ASender: TObject;
      sender: OleVariant;
      const eventArgs: _EasyUADataChangeNotificationEventArgs);
  end;

procedure TIntegerClientEventHandlers124.OnDataChangeNotification(
  ASender: TObject;
  sender: OleVariant;
  const eventArgs: _EasyUADataChangeNotificationEventArgs);
var
  stateAsInteger: Integer;
begin
  // Obtain the integer state we have passed in.
  stateAsInteger := eventArgs.Arguments.State;
  if eventArgs.Succeeded then
      WriteLn(stateAsInteger, ': ', eventArgs.AttributeData.ToString)
  else
      WriteLn(stateAsInteger, ' *** Failure: ', eventArgs.ErrorMessageBrief);
end;


class procedure SubscribeMultipleMonitoredItems.StateAsInteger;
var
  Arguments: OleVariant;
  Client: TEasyUAClient;
  ClientEventHandlers: TIntegerClientEventHandlers124;
  Handle: Cardinal;
  HandleArray: OleVariant;
  I: Cardinal;
  MonitoredItemArguments1, MonitoredItemArguments2, MonitoredItemArguments3:
    _EasyUAMonitoredItemArguments;
  MonitoringParameters: _UAMonitoringParameters;
begin
  // Instantiate the client object and hook events
  Client := TEasyUAClient.Create(nil);
  ClientEventHandlers := TIntegerClientEventHandlers124.Create;
  Client.OnDataChangeNotification := ClientEventHandlers.OnDataChangeNotification;

  WriteLn('Subscribing...');
  MonitoringParameters := CoUAMonitoringParameters.Create;
  MonitoringParameters.SamplingInterval := 1000;

  MonitoredItemArguments1 := CoEasyUAMonitoredItemArguments.Create;
  MonitoredItemArguments1.EndpointDescriptor.UrlString := 
    //'http://opcua.demo-this.com:51211/UA/SampleServer';
    //'https://opcua.demo-this.com:51212/UA/SampleServer/';
    'opc.tcp://opcua.demo-this.com:51210/UA/SampleServer';
  MonitoredItemArguments1.NodeDescriptor.NodeId.ExpandedText := 'nsu=http://test.org/UA/Data/ ;i=10845';
  MonitoredItemArguments1.MonitoringParameters := MonitoringParameters;
  MonitoredItemArguments1.SetState(1);  // An integer we have chosen to identify the subscription

  MonitoredItemArguments2 := CoEasyUAMonitoredItemArguments.Create;
  MonitoredItemArguments2.EndpointDescriptor.UrlString := 
    //'http://opcua.demo-this.com:51211/UA/SampleServer';
    //'https://opcua.demo-this.com:51212/UA/SampleServer/';
    'opc.tcp://opcua.demo-this.com:51210/UA/SampleServer';
  MonitoredItemArguments2.NodeDescriptor.NodeId.ExpandedText := 'nsu=http://test.org/UA/Data/ ;i=10853';
  MonitoredItemArguments2.MonitoringParameters := MonitoringParameters;
  MonitoredItemArguments2.SetState(2);  // An integer we have chosen to identify the subscription

  MonitoredItemArguments3 := CoEasyUAMonitoredItemArguments.Create;
  MonitoredItemArguments3.EndpointDescriptor.UrlString := 
    //'http://opcua.demo-this.com:51211/UA/SampleServer';
    //'https://opcua.demo-this.com:51212/UA/SampleServer/';
    'opc.tcp://opcua.demo-this.com:51210/UA/SampleServer';
  MonitoredItemArguments3.NodeDescriptor.NodeId.ExpandedText := 'nsu=http://test.org/UA/Data/ ;i=10855';
  MonitoredItemArguments3.MonitoringParameters := MonitoringParameters;
  MonitoredItemArguments3.SetState(3);  // An integer we have chosen to identify the subscription

  Arguments := VarArrayCreate([0, 2], varVariant);
  Arguments[0] := MonitoredItemArguments1;
  Arguments[1] := MonitoredItemArguments2;
  Arguments[2] := MonitoredItemArguments3;

  TVarData(HandleArray).VType := varArray or varVariant;
  TVarData(HandleArray).VArray := PVarArray(
    Client.SubscribeMultipleMonitoredItems(Arguments));

  for I := VarArrayLowBound(HandleArray, 1) to VarArrayHighBound(HandleArray, 1) do
  begin
      Handle := Cardinal(HandleArray[I]);
      WriteLn('HandleArray[', I, ']: ', Handle);
  end;

  WriteLn('Processing monitored item changed events for 10 seconds...');
  PumpSleep(10*1000);

  WriteLn('Unsubscribing...');
  Client.UnsubscribeAllMonitoredItems;

  WriteLn('Waiting for 5 seconds...');
  Sleep(5*1000);

  WriteLn('Finished.');
  VarClear(HandleArray);
  VarClear(Arguments);
  FreeAndNil(Client);
  FreeAndNil(ClientEventHandlers);
end;
// This example shows how to subscribe to changes of multiple monitored items
// and display each change, identifying the different subscriptions by an
// integer.

class ClientEvents {
    function DataChangeNotification($Sender, $E)
    {
        // Obtain the integer state we have passed in.
        $stateAsInteger = $E->Arguments->State;
        if ($E->Succeeded)
            printf("d: s\n", $stateAsInteger, $E->AttributeData);
        else
            printf("d *** Failures\n", $stateAsInteger, $E->ErrorMessageBrief);
    }
}


// Instantiate the client object and hook events
$Client = new COM("OpcLabs.EasyOpc.UA.EasyUAClient");
$ClientEvents = new ClientEvents();
com_event_sink($Client, $ClientEvents, "DEasyUAClientEvents");

printf("Subscribing...\n");
$MonitoringParameters = new COM("OpcLabs.EasyOpc.UA.UAMonitoringParameters");
$MonitoringParameters->SamplingInterval = 1000;

$MonitoredItemArguments1 = new COM("OpcLabs.EasyOpc.UA.OperationModel.EasyUAMonitoredItemArguments");
$MonitoredItemArguments1->EndpointDescriptor->UrlString = 
    //"http://opcua.demo-this.com:51211/UA/SampleServer";
    //"https://opcua.demo-this.com:51212/UA/SampleServer/";
    "opc.tcp://opcua.demo-this.com:51210/UA/SampleServer";
$MonitoredItemArguments1->NodeDescriptor->NodeId->ExpandedText = "nsu=http://test.org/UA/Data/ ;i=10845";
$MonitoredItemArguments1->MonitoringParameters = $MonitoringParameters;
$MonitoredItemArguments1->SetState(1);  // An integer we have chosen to identify the subscription

$MonitoredItemArguments2 = new COM("OpcLabs.EasyOpc.UA.OperationModel.EasyUAMonitoredItemArguments");
$MonitoredItemArguments2->EndpointDescriptor->UrlString = 
    //"http://opcua.demo-this.com:51211/UA/SampleServer";
    //"https://opcua.demo-this.com:51212/UA/SampleServer/";
    "opc.tcp://opcua.demo-this.com:51210/UA/SampleServer";
$MonitoredItemArguments2->NodeDescriptor->NodeId->ExpandedText = "nsu=http://test.org/UA/Data/ ;i=10853";
$MonitoredItemArguments2->MonitoringParameters = $MonitoringParameters;
$MonitoredItemArguments2->SetState(2);  // An integer we have chosen to identify the subscription

$MonitoredItemArguments3 = new COM("OpcLabs.EasyOpc.UA.OperationModel.EasyUAMonitoredItemArguments");
$MonitoredItemArguments3->EndpointDescriptor->UrlString = 
    //"http://opcua.demo-this.com:51211/UA/SampleServer";
    //"https://opcua.demo-this.com:51212/UA/SampleServer/";
    "opc.tcp://opcua.demo-this.com:51210/UA/SampleServer";
$MonitoredItemArguments3->NodeDescriptor->NodeId->ExpandedText = "nsu=http://test.org/UA/Data/ ;i=10855";
$MonitoredItemArguments3->MonitoringParameters = $MonitoringParameters;
$MonitoredItemArguments3->SetState(3);  // An integer we have chosen to identify the subscription

$arguments[0] = $MonitoredItemArguments1;
$arguments[1] = $MonitoredItemArguments2;
$arguments[2] = $MonitoredItemArguments3;

$handleArray = $Client->SubscribeMultipleMonitoredItems($arguments);

for ($i = 0; $i < count($handleArray); $i++)
{
    printf("handleArray[d]d\n", $i, $handleArray[$i]);
}

printf("Processing monitored item changed events for 10 seconds...\n");
$startTime = time(); do { com_message_pump(1000); } while (time() < $startTime + 10);

printf("Unsubscribing...\n");
$Client->UnsubscribeAllMonitoredItems;

printf("Waiting for 5 seconds...\n");
$startTime = time(); do { com_message_pump(1000); } while (time() < $startTime + 5);
Rem This example shows how to subscribe to changes of multiple monitored items
Rem and display each change, identifying the different subscriptions by an
Rem integer.

' The client object, with events
'Public WithEvents Client6 As EasyUAClient

Public Sub SubscribeMultipleMonitoredItems_StateAsInteger_Command_Click()
    OutputText = ""

    Set Client6 = New EasyUAClient

    OutputText = OutputText & "Subscribing..." & vbCrLf
    Dim MonitoringParameters As New UAMonitoringParameters
    MonitoringParameters.SamplingInterval = 1000
    
    Dim MonitoredItemArguments1 As New EasyUAMonitoredItemArguments
    MonitoredItemArguments1.endpointDescriptor.UrlString = "opc.tcp://opcua.demo-this.com:51210/UA/SampleServer"
    MonitoredItemArguments1.nodeDescriptor.NodeId.expandedText = "nsu=http://test.org/UA/Data/ ;i=10845"
    Set MonitoredItemArguments1.MonitoringParameters = MonitoringParameters
    MonitoredItemArguments1.SetState 1 ' An integer we have chosen to identify the subscription
    
    Dim MonitoredItemArguments2 As New EasyUAMonitoredItemArguments
    MonitoredItemArguments2.endpointDescriptor.UrlString = "opc.tcp://opcua.demo-this.com:51210/UA/SampleServer"
    MonitoredItemArguments2.nodeDescriptor.NodeId.expandedText = "nsu=http://test.org/UA/Data/ ;i=10853"
    Set MonitoredItemArguments2.MonitoringParameters = MonitoringParameters
    MonitoredItemArguments2.SetState 2 ' An integer we have chosen to identify the subscription
    
    Dim MonitoredItemArguments3 As New EasyUAMonitoredItemArguments
    MonitoredItemArguments3.endpointDescriptor.UrlString = "opc.tcp://opcua.demo-this.com:51210/UA/SampleServer"
    MonitoredItemArguments3.nodeDescriptor.NodeId.expandedText = "nsu=http://test.org/UA/Data/ ;i=10855"
    Set MonitoredItemArguments3.MonitoringParameters = MonitoringParameters
    MonitoredItemArguments3.SetState 3 ' An integer we have chosen to identify the subscription
    
    Dim arguments(2) As Variant
    Set arguments(0) = MonitoredItemArguments1
    Set arguments(1) = MonitoredItemArguments2
    Set arguments(2) = MonitoredItemArguments3
    Dim handleArray As Variant
    handleArray = Client6.SubscribeMultipleMonitoredItems(arguments)

    Dim i As Long: For i = LBound(handleArray) To UBound(handleArray)
        OutputText = OutputText & "handleArray(" & i & "): " & handleArray(i) & vbCrLf
    Next

    OutputText = OutputText & "Processing monitored item changed events for 10 seconds..." & vbCrLf
    Pause 10000

    OutputText = OutputText & "Unsubscribing..." & vbCrLf
    Call Client6.UnsubscribeAllMonitoredItems

    OutputText = OutputText & "Waiting for 5 seconds..." & vbCrLf
    Pause 5000

    Set Client2 = Nothing
    OutputText = OutputText & "Finished." & vbCrLf
End Sub

Public Sub Client6_DataChangeNotification(ByVal sender As Variant, ByVal eventArgs As EasyUADataChangeNotificationEventArgs)
    ' Obtain the integer state we have passed in.
    Dim stateAsInteger As Integer: stateAsInteger = eventArgs.arguments.State
    If eventArgs.Succeeded Then
        OutputText = OutputText & stateAsInteger & ": " & eventArgs.AttributeData & vbCrLf
    Else
        OutputText = OutputText & stateAsInteger & " *** Failure: " & eventArgs.ErrorMessageBrief & vbCrLf
    End If
End Sub

 

 

.NET

// This example shows how to subscribe to changes of multiple monitored items
// and display each change, identifying the different subscriptions by an
// object.

using System;
using System.Threading;
using OpcLabs.EasyOpc.UA;
using OpcLabs.EasyOpc.UA.OperationModel;

namespace UADocExamples._EasyUAClient
{
    partial class SubscribeMultipleMonitoredItems
    {
        class CustomObject
        {
            public CustomObject(string name)
            {
                Name = name;
            }

            public string Name { get; }
        }
        

        public static void StateAsObject()
        {
            UAEndpointDescriptor endpointDescriptor =
                "opc.tcp://opcua.demo-this.com:51210/UA/SampleServer";
            // or "http://opcua.demo-this.com:51211/UA/SampleServer" (currently not supported)
            // or "https://opcua.demo-this.com:51212/UA/SampleServer/"

            // Instantiate the client object and hook events
            var client = new EasyUAClient();
            client.DataChangeNotification += ClientOnDataChangeNotification_StateAsObject;

            Console.WriteLine("Subscribing...");
            int[] handleArray = client.SubscribeMultipleMonitoredItems(new[]
                {
                    new EasyUAMonitoredItemArguments(null, endpointDescriptor, 
                        "nsu=http://test.org/UA/Data/ ;i=10845", 1000)
                        {State = new CustomObject("First")},    // A custom object that corresponds to the subscription
                    new EasyUAMonitoredItemArguments(null, endpointDescriptor, 
                        "nsu=http://test.org/UA/Data/ ;i=10853", 1000)
                        {State = new CustomObject("Second")},    // A custom object that corresponds to the subscription
                    new EasyUAMonitoredItemArguments(null, endpointDescriptor, 
                        "nsu=http://test.org/UA/Data/ ;i=10855", 1000)
                        {State = new CustomObject("Third")},    // A custom object that corresponds to the subscription
                });

            for (int i = 0; i < handleArray.Length; i++)
                Console.WriteLine($"handleArray[{i}]: {handleArray[i]}");

            Console.WriteLine("Processing monitored item changed events for 10 seconds...");
            Thread.Sleep(10 * 1000);

            Console.WriteLine("Unsubscribing...");
            client.UnsubscribeAllMonitoredItems();

            Console.WriteLine("Waiting for 5 seconds...");
            Thread.Sleep(5 * 1000);

            Console.WriteLine("Finished.");
        }

        static void ClientOnDataChangeNotification_StateAsObject(object sender, EasyUADataChangeNotificationEventArgs eventArgs)
        {
            // Obtain the custom object we have passed in.
            var stateAsObject = (CustomObject) eventArgs.Arguments.State;

            // Display the data
            if (eventArgs.Succeeded)
                Console.WriteLine($"{stateAsObject.Name}: {eventArgs.AttributeData}");
            else
                Console.WriteLine($"{stateAsObject.Name} *** Failure: {eventArgs.ErrorMessageBrief}");
        }
    }
}
# This example shows how to subscribe to changes of multiple monitored items
# and display each change, identifying the different subscriptions by an
# object.

# The QuickOPC package is needed. Install it using "pip install opclabs_quickopc".
import opclabs_quickopc
import time

# Import .NET namespaces.
from OpcLabs.EasyOpc.UA import *
from OpcLabs.EasyOpc.UA.OperationModel import *


class CustomObject(object):
    def __init__(self, name):
        self.name = name


def dataChangeNotification(sender, eventArgs):
    # Obtain the custom object we have passed in.
    stateAsObject = eventArgs.Arguments.State
    # Display value.
    if eventArgs.Succeeded:
        print(stateAsObject.name, ': ', eventArgs.AttributeData.Value, sep='')
    else:
        print(stateAsObject.name, ' *** Failure: ', eventArgs.ErrorMessageBrief, sep='')


endpointDescriptor = UAEndpointDescriptor('opc.tcp://opcua.demo-this.com:51210/UA/SampleServer')
# or 'http://opcua.demo-this.com:51211/UA/SampleServer' (currently not supported)
# or 'https://opcua.demo-this.com:51212/UA/SampleServer/'

# Instantiate the client object and hook events.
client = EasyUAClient()
client.DataChangeNotification += dataChangeNotification

print('Subscribing...')
monitoredItemArguments1 = EasyUAMonitoredItemArguments(
    None,
    endpointDescriptor,
    UANodeDescriptor('nsu=http://test.org/UA/Data/ ;i=10845'),
    UAMonitoringParameters(1000))
monitoredItemArguments1.State = CustomObject('First')    # a custom object that corresponds to the subscription
monitoredItemArguments2 = EasyUAMonitoredItemArguments(
    None,
    endpointDescriptor,
    UANodeDescriptor('nsu=http://test.org/UA/Data/ ;i=10853'),
    UAMonitoringParameters(1000))
monitoredItemArguments2.State = CustomObject('Second')    # a custom object that corresponds to the subscription
monitoredItemArguments3 = EasyUAMonitoredItemArguments(
    None,
    endpointDescriptor,
    UANodeDescriptor('nsu=http://test.org/UA/Data/ ;i=10855'),
    UAMonitoringParameters(1000))
monitoredItemArguments3.State = CustomObject('Third')    # a custom object that corresponds to the subscription

client.SubscribeMultipleMonitoredItems([
    monitoredItemArguments1,
    monitoredItemArguments2,
    monitoredItemArguments3,
    ])

print('Processing data change events for 10 seconds...')
time.sleep(10)

print('Unsubscribing...')
client.UnsubscribeAllMonitoredItems()

print('Waiting for 5 seconds...')
time.sleep(5)

print('Finished.')
' This example shows how to subscribe to changes of multiple monitored items
' and display each change, identifying the different subscriptions by an
' object.

Imports OpcLabs.EasyOpc.UA
Imports OpcLabs.EasyOpc.UA.OperationModel

Namespace _EasyUAClient
    Partial Friend Class SubscribeMultipleMonitoredItems
        Class CustomObject
            Public Sub New(ByVal name As String)
                _Name = name
            End Sub
            Public ReadOnly Property Name As String
                Get
                    Return _Name
                End Get
            End Property
            Private ReadOnly _Name As String
        End Class

        Public Shared Sub StateAsObject()

            ' Define which server we will work with.
            Dim endpointDescriptor As UAEndpointDescriptor =
                    "opc.tcp://opcua.demo-this.com:51210/UA/SampleServer"
            ' or "http://opcua.demo-this.com:51211/UA/SampleServer" (currently not supported)
            ' or "https://opcua.demo-this.com:51212/UA/SampleServer/"

            ' Instantiate the client object and hook events
            Dim client = New EasyUAClient()
            AddHandler client.DataChangeNotification, AddressOf ClientOnDataChangeNotification_StateAsObject

            Console.WriteLine("Subscribing...")
            Dim handleArray() As Integer = client.SubscribeMultipleMonitoredItems(New EasyUAMonitoredItemArguments() _
                { _
                    New EasyUAMonitoredItemArguments(Nothing, endpointDescriptor, _
                        "nsu=http://test.org/UA/Data/ ;i=10845", 1000) _
                         With {.State = New CustomObject("First")}, _
                    New EasyUAMonitoredItemArguments(Nothing, endpointDescriptor, _
                        "nsu=http://test.org/UA/Data/ ;i=10853", 1000) _
                         With {.State = New CustomObject("Second")}, _
                    New EasyUAMonitoredItemArguments(Nothing, endpointDescriptor, _
                        "nsu=http://test.org/UA/Data/ ;i=10855", 1000) _
                        With {.State = New CustomObject("Third")} _
                } _
            ) ' A custom object that corresponds to the subscription

            For i As Integer = 0 To handleArray.Length - 1
                Console.WriteLine("handleArray[{0}]: {1}", i, handleArray(i))
            Next i

            Console.WriteLine("Processing monitored item changed events for 10 seconds...")
            Threading.Thread.Sleep(10 * 1000)

            Console.WriteLine("Unsubscribing...")
            client.UnsubscribeAllMonitoredItems()

            Console.WriteLine("Waiting for 5 seconds...")
            Threading.Thread.Sleep(5 * 1000)

            Console.WriteLine("Finished.")
        End Sub

        Private Shared Sub ClientOnDataChangeNotification_StateAsObject(ByVal sender As Object, ByVal eventArgs As EasyUADataChangeNotificationEventArgs)
            ' Obtain the custom object we have passed in.
            Dim stateAsObject As CustomObject = CType(eventArgs.Arguments.State, CustomObject)

            ' Display the data
            If eventArgs.Succeeded Then
                Console.WriteLine("{0}: {1}", stateAsObject.Name, eventArgs.AttributeData)
            Else
                Console.WriteLine("{0} *** Failure: {1}", stateAsObject.Name, eventArgs.ErrorMessageBrief)
            End If
        End Sub
    End Class
End Namespace

COM

PHP: We have found no way of retrieving the original PHP object from State (COM VARIANT). Use other data types (such as integers) for State in PHP: Examples - OPC Unified Architecture - Identify subscriptions by an integer state .
// This example shows how to subscribe to changes of multiple monitored items
// and display each change, identifying the different subscriptions by an
// object.

type
  TCustomObject = class
    Name: string;
    constructor Create(name: string);
  end;

constructor TCustomObject.Create(name: string);
begin
  Self.Name := name;
end;

type
  TObjectClientEventHandlers125 = class
    procedure OnDataChangeNotification(
      ASender: TObject;
      sender: OleVariant;
      const eventArgs: _EasyUADataChangeNotificationEventArgs);
  end;

procedure TObjectClientEventHandlers125.OnDataChangeNotification(
  ASender: TObject;
  sender: OleVariant;
  const eventArgs: _EasyUADataChangeNotificationEventArgs);
var
  stateAsObject: TCustomObject;
begin
  // Obtain the custom object we have passed in.
  stateAsObject := TCustomObject(INT_PTR(eventArgs.Arguments.State));
  // Display the data
  if eventArgs.Succeeded then
      WriteLn(stateAsObject.Name, ': ', eventArgs.AttributeData.ToString)
  else
      WriteLn(stateAsObject.Name, ' *** Failure: ', eventArgs.ErrorMessageBrief);
end;

class procedure SubscribeMultipleMonitoredItems.StateAsObject;
var
  Arguments: OleVariant;
  Client: TEasyUAClient;
  ClientEventHandlers: TObjectClientEventHandlers125;
  Handle: Cardinal;
  HandleArray: OleVariant;
  I: Cardinal;
  MonitoredItemArguments1, MonitoredItemArguments2, MonitoredItemArguments3:
    _EasyUAMonitoredItemArguments;
  MonitoringParameters: _UAMonitoringParameters;
  CustomObject1, CustomObject2, CustomObject3: TCustomObject;
begin
  CustomObject1 := TCustomObject.Create('First');
  CustomObject2 := TCustomObject.Create('Second');
  CustomObject3 := TCustomObject.Create('Third');

  // Instantiate the client object and hook events
  Client := TEasyUAClient.Create(nil);
  ClientEventHandlers := TObjectClientEventHandlers125.Create;
  Client.OnDataChangeNotification := ClientEventHandlers.OnDataChangeNotification;

  WriteLn('Subscribing...');
  MonitoringParameters := CoUAMonitoringParameters.Create;
  MonitoringParameters.SamplingInterval := 1000;

  MonitoredItemArguments1 := CoEasyUAMonitoredItemArguments.Create;
  MonitoredItemArguments1.EndpointDescriptor.UrlString := 
    //'http://opcua.demo-this.com:51211/UA/SampleServer';
    //'https://opcua.demo-this.com:51212/UA/SampleServer/';
    'opc.tcp://opcua.demo-this.com:51210/UA/SampleServer';
  MonitoredItemArguments1.NodeDescriptor.NodeId.ExpandedText := 'nsu=http://test.org/UA/Data/ ;i=10845';
  MonitoredItemArguments1.MonitoringParameters := MonitoringParameters;
  MonitoredItemArguments1.SetState(INT_PTR(CustomObject1));  // A custom object that corresponds to the subscription

  MonitoredItemArguments2 := CoEasyUAMonitoredItemArguments.Create;
  MonitoredItemArguments2.EndpointDescriptor.UrlString := 
    //'http://opcua.demo-this.com:51211/UA/SampleServer';
    //'https://opcua.demo-this.com:51212/UA/SampleServer/';
    'opc.tcp://opcua.demo-this.com:51210/UA/SampleServer';
  MonitoredItemArguments2.NodeDescriptor.NodeId.ExpandedText := 'nsu=http://test.org/UA/Data/ ;i=10853';
  MonitoredItemArguments2.MonitoringParameters := MonitoringParameters;
  MonitoredItemArguments2.SetState(INT_PTR(CustomObject2));  // A custom object that corresponds to the subscription

  MonitoredItemArguments3 := CoEasyUAMonitoredItemArguments.Create;
  MonitoredItemArguments3.EndpointDescriptor.UrlString := 
    //'http://opcua.demo-this.com:51211/UA/SampleServer';
    //'https://opcua.demo-this.com:51212/UA/SampleServer/';
    'opc.tcp://opcua.demo-this.com:51210/UA/SampleServer';
  MonitoredItemArguments3.NodeDescriptor.NodeId.ExpandedText := 'nsu=http://test.org/UA/Data/ ;i=10855';
  MonitoredItemArguments3.MonitoringParameters := MonitoringParameters;
  MonitoredItemArguments3.SetState(INT_PTR(CustomObject3));  // A custom object that corresponds to the subscription

  Arguments := VarArrayCreate([0, 2], varVariant);
  Arguments[0] := MonitoredItemArguments1;
  Arguments[1] := MonitoredItemArguments2;
  Arguments[2] := MonitoredItemArguments3;

  TVarData(HandleArray).VType := varArray or varVariant;
  TVarData(HandleArray).VArray := PVarArray(
    Client.SubscribeMultipleMonitoredItems(Arguments));

  for I := VarArrayLowBound(HandleArray, 1) to VarArrayHighBound(HandleArray, 1) do
  begin
      Handle := Cardinal(HandleArray[I]);
      WriteLn('HandleArray[', I, ']: ', Handle);
  end;

  WriteLn('Processing monitored item changed events for 10 seconds...');
  PumpSleep(10*1000);

  WriteLn('Unsubscribing...');
  Client.UnsubscribeAllMonitoredItems;

  WriteLn('Waiting for 5 seconds...');
  Sleep(5*1000);

  WriteLn('Finished.');
  VarClear(HandleArray);
  VarClear(Arguments);
  FreeAndNil(Client);
  FreeAndNil(ClientEventHandlers);
  FreeAndNil(CustomObject1);
  FreeAndNil(CustomObject2);
  FreeAndNil(CustomObject3);
end;

 

See Also

Examples - OPC Data Access

Examples - OPC Unified Architecture